#include <iostream>
#include <sstream>
#include <fstream>
#include <stdlib.h>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "tokens.h"
#include "LinkedList.h"
#include "scene.h"

#define NUMARGS 4

using namespace std;
using namespace glm;


Symbol nextToken(ifstream &file) {
    char c;
    string build = "";
    Symbol ret;

    file >> c;
    while (isspace(c) && file.good()) {
        file >> c;
    }

    build.push_back(c);
    if (isalpha(c)) {
        c = file.peek();
        while(isalpha(c) || c == '_') {
            file >> c;
            build.push_back(c);
            c = file.peek();
        }

        ret.isToken = true;
        ret.t = lookUpToken(build);

        return ret;
    }
    else if (c == '/') {
        file >> c;
        if (c == '/') {
            ret.isToken = true;
            string line;
            getline(file, line);

            return nextToken(file);
        }
        
        cout << "incorrect format: found /" << c << " expected //\n";
        exit(1);
    }
    else if (isdigit(c) || c == '.' || c == '-') {
        bool foundDot = (c == '.');
        bool isNeg = (c == '-');

        c = file.peek();
        while (isdigit(c) || c == '.') {
            file >> c;
            build.push_back(c);
            if (c == '.') {
                if (foundDot) {
                    cout << "Incorrect number format\n";
                    exit(1);
                }

                foundDot = true;
            }

            c = file.peek();
        }

        ret.isToken = false;
        istringstream(build) >> ret.num;

        return ret;
    }
    else {
        ret.isToken = true;
        ret.t = lookUpToken(build);

        return ret;
    }
}

Symbol matchToken(ifstream &file, Symbol cur, Token match) {
    if (cur.isToken && cur.t == match) {
        return nextToken(file);
    }
    
    cout << "expected '" << lookUpName(match) << "' found '" <<
        lookUpName(cur.t) << "'" << endl;
    exit(1);
}

float *parseVector(ifstream &file, Symbol curToken, Symbol *nToken,
        int num) {
    float *numFill = new float[num];
    curToken = matchToken(file, curToken, LAngle);

    for (int i = 0; i < num; i++) {
        if (curToken.isToken) {
            cout << "expected number, found " << 
            tokenList[curToken.t] << endl;
            exit(1);
        }
        numFill[i] = curToken.num;
        curToken = nextToken(file);
        if (i < num - 1) 
            curToken = matchToken(file, curToken, Comma); 
    }

    curToken = matchToken(file, curToken, RAngle);

    *(nToken) = curToken;
    return numFill;
}

Symbol WCamera::parse(ifstream &file, Symbol curToken) {
    Symbol nToken = matchToken(file, curToken, LBrace);
    int i;
    float *n;

    while (nToken.t != RBrace) {
        if (nToken.t == Location) {
            nToken = matchToken(file, nToken, Location);
            n = parseVector(file, nToken, &nToken, 3);

            location = vec3(n[0], n[1], n[2]);
            delete n;
        }
        else if (nToken.t == Up) {
            nToken = matchToken(file, nToken, Up);
            n = parseVector(file, nToken, &nToken, 3);
            up = vec3(n[0], n[1], n[2]);
            delete n;
        }
        else if (nToken.t == Right) {
            nToken = matchToken(file, nToken, Right);

            n = parseVector(file, nToken, &nToken, 3);

            right = vec3(n[0], n[1], n[2]);
            delete n;
        }
        else if (nToken.t == LookAt) {
            nToken = matchToken(file, nToken, LookAt);

            n = parseVector(file, nToken, &nToken, 3);

            lookAt = vec3(n[0], n[1], n[2]);
        }
    }

    
    return matchToken(file, nToken, RBrace);
}

void WCamera::print() {
    printf("Camera vectors:\n\tlocation: %f, %f, %f\n\tup"
        ": %f, %f, %f\n\tright: %f, %f, %f\n\tlookAt: %f, %f, %f\n",
        location.x, location.y, location.z, 
        up.x, up.y, up.z,
        right.x, right.y, right.z, 
        lookAt.x, lookAt.y, lookAt.z);
}


Symbol WLight::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);
    
    numFill = parseVector(file, curToken, &curToken, 3);
    location = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Color);
    curToken = matchToken(file, curToken, RGB);


    numFill = parseVector(file, curToken, &curToken, 3);
    curToken = matchToken(file, curToken, RBrace);
    color = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

   
    return curToken;
}

void WLight::print() {
    printf("Light vectors:\n\tlocation: %f, %f, %f\n\tcolor"
        ": %f, %f, %f\n",
        location.x, location.y, location.z, 
        color.x, color.y, color.z);
}



/* global because we're cheating */
Scene globalScene;

Symbol parsePigment(ifstream &file, Symbol curToken, MPigment *p) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);
    curToken = matchToken(file, curToken, Color);

    if (curToken.t == RGB) {
        curToken = matchToken(file, curToken, RGB);

        numFill = parseVector(file, curToken, &curToken, 3);
        p->color = vec4(numFill[0], numFill[1], numFill[2], 0);
        delete numFill;
    }
    else {
        curToken = matchToken(file, curToken, RGBF);
        numFill = parseVector(file, curToken, &curToken, 4);
        p->color = vec4(numFill[0], numFill[1], numFill[2], numFill[3]);
        delete numFill;
    }

    

    return matchToken(file, curToken, RBrace);
}

Symbol parseFinish(ifstream &file, Symbol curToken, MFinish *f) {
    curToken = matchToken(file, curToken, LBrace);

    while (curToken.t != RBrace) {
        if (curToken.t == Ambient) {
            curToken = matchToken(file, curToken, Ambient);
            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->ambient = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Diffuse) {
            curToken = matchToken(file, curToken, Diffuse);
            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->diffuse = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Specular) {
            curToken = matchToken(file, curToken, Specular);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->specular = curToken.num;
            curToken = nextToken(file);
        }
        
        if (curToken.t == Roughness) {
            curToken = matchToken(file, curToken, Roughness);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->roughness = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Reflection) {
            curToken = matchToken(file, curToken, Reflection);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->reflection = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Refraction) {
            curToken = matchToken(file, curToken, Refraction);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->refraction = curToken.num;
            curToken = nextToken(file);
        }


        if (curToken.t == IOR) {
            curToken = matchToken(file, curToken, IOR);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->ior = curToken.num;
            curToken = nextToken(file);
        }
    }

    return matchToken(file, curToken, RBrace);
}

Symbol parseTranslate(ifstream &file, Symbol curToken, mat4 *trans) {
    float *fillNum;

    fillNum = parseVector(file, curToken, &curToken, 3);

    *(trans) = translate(mat4(1.0f), 
            vec3(fillNum[0], fillNum[1], fillNum[2])) * (*(trans));

    delete fillNum;
    return curToken;
}

Symbol parseRotate(ifstream &file, Symbol curToken, mat4 *trans) {
    float *fillNum;

    fillNum = parseVector(file, curToken, &curToken, 3);

    *(trans) = 
        rotate(mat4(1.0), fillNum[2], vec3(0.0f, 0.0f, 1.0f)) *
        rotate(mat4(1.0), fillNum[1], vec3(0.0f, 1.0f, 0.0f)) *
        rotate(mat4(1.0), fillNum[0], vec3(1.0f, 0.0f, 0.0f)) * (*(trans));

    delete fillNum;
    return curToken;
}

Symbol parseScale(ifstream &file, Symbol curToken, mat4 *trans) {
    float *fillNum;
    
    fillNum = parseVector(file, curToken, &curToken, 3);

    *(trans) = scale(mat4(1.0), vec3(fillNum[0], fillNum[1], fillNum[2]));

    delete fillNum;
    return curToken;
}

Symbol parseMaterials(ifstream &file, Symbol curToken, Geometry *g) {
    while (curToken.t != RBrace) {
        if (curToken.t == Pigment) {
            curToken = parsePigment(file, 
                matchToken(file, curToken, Pigment), &(g->pigment));
        }
        else if (curToken.t == Finish) {
            curToken = parseFinish(file,
                matchToken(file, curToken, Finish), &(g->finish));
        }
        else if (curToken.t == Translate) {
            curToken = parseTranslate(file,
                matchToken(file, curToken, Translate), &(g->transform));
        }
        else if (curToken.t == Rotate) {
            curToken = parseRotate(file,
                matchToken(file, curToken, Rotate), &(g->transform));
        }
        else if (curToken.t == Scale) {
            curToken = parseScale(file,
                matchToken(file, curToken, Scale), &(g->transform));
        }
        else {
            /*if (curToken.isToken)
                cout << tokenList[curToken.t] << endl;
            else cout << curToken.num << endl;
            curToken = nextToken(file);*/
            cout << "Unexpected token: " << tokenList[curToken.t] << endl;
            exit(1);
        }
    }

    return matchToken(file, curToken, RBrace);
}

Symbol WSphere::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);

    numFill = parseVector(file, curToken, &curToken, 3);
    center = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "expected number, found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }

    radius = curToken.num;
    curToken = nextToken(file);

    curToken = parseMaterials(file, curToken, this);
    return curToken;
}

Symbol WPlane::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);

    numFill = parseVector(file, curToken, &curToken, 3);
    normal = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "Expected number: found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }

    distance = curToken.num;
    curToken = nextToken(file);
    curToken = parseMaterials(file, curToken, this);

    return curToken;
}

Symbol WTriangle::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);

    numFill = parseVector(file, curToken, &curToken, 3);
    p1 = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    p2 = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    p3 = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;
    
    curToken = parseMaterials(file, curToken, this);

    return curToken;
}

Symbol WCone::parse(ifstream &file, Symbol curToken) {
    float *numFill;
    
    curToken = matchToken(file, curToken, LBrace);
    numFill = parseVector(file, curToken, &curToken, 3);
    base = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "Expected number, found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }
    baseR = curToken.num;

    curToken = nextToken(file);
    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    top = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "Expected number, found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }
    topR = curToken.num;
    
    curToken = nextToken(file);
    curToken = parseMaterials(file, curToken, this);

    return curToken;
}

Symbol WBox::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);
    numFill = parseVector(file, curToken, &curToken, 3);
    min = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    max = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = parseMaterials(file, curToken, this);

    return curToken;
}

Symbol parseGeometry(ifstream &file, Symbol curToken) {
    Geometry *g;
    if (curToken.t == Sphere) {
        printf("Parsing sphere\n");
        g = new WSphere();
        curToken = g->parse(file, matchToken(file, curToken, Sphere));
    }
    else if (curToken.t == Plane) {
        printf("Parsing plane\n");
        g = new WPlane();
        curToken = g->parse(file, matchToken(file, curToken, Plane));
    }
    else if (curToken.t == Triangle) {
        printf("Parsing triangle\n");
        g = new WTriangle();
        curToken = g->parse(file, matchToken(file, curToken, Triangle));
    }
    else if (curToken.t == Box) {
        printf("Parsing box\n");
        g = new WBox();
        curToken = g->parse(file, matchToken(file, curToken, Box));
    }
    else if (curToken.t == Cone) {
        printf("Parsing cone\n");
        g = new WCone();
        curToken = g->parse(file, matchToken(file, curToken, Cone));
    }
    else {
        if (curToken.isToken) {
            cout << "Expected Object, found " << 
            tokenList[curToken.t] << endl;
        }
        else {
            cout << "Expected Object, found " << curToken.num << endl;
        }
        exit(1);
    }

    globalScene.geometry.add(g);
    return curToken;
}

Symbol parseObject(ifstream& file, Symbol curToken) {
    if (curToken.t == Camera) {
        cout << "Parsing camera\n";
        WCamera *c = new WCamera();
        curToken = c->parse(file, matchToken(file, curToken, Camera));
        if (globalScene.camera != NULL) {
            cout << "Cannot define multiple cameras\n";
            exit(1);
        }

        globalScene.camera = c;
        return curToken;
    }
    else if (curToken.t == Light) {
        cout << "Parsing light\n";
        WLight *l = new WLight();
        curToken = l->parse(file, matchToken(file, curToken, Light));
        globalScene.lights.add(l);

        return curToken;
    }
    else {
        return parseGeometry(file, curToken);
    }
}

Symbol parseStatement(ifstream &file, Symbol curToken) {
    if (curToken.isToken && curToken.t != Eof) {
        return parseObject(file, curToken);
    }
    else if (curToken.t != Eof) {
        cout << "Expected object, found number: " << curToken.num << endl;
        exit(1);
    }
    else {
        return curToken;
    }
}

void parseScene(ifstream &file) {
    Symbol curToken = nextToken(file);
    while (curToken.t != Eof && file.good()) {
        curToken = parseStatement(file, curToken);
    }
}

int main(int argc, char *argv[]) {
    string input, temp, line;
    ifstream file;
    int width, height;
    int argOptions = 0;

    if (argc < NUMARGS) {
        cout << "Usage: raytrace Width Height -I Filename\n";
        exit(1);
    }

    width = atoi(argv[++argOptions]);
    height = atoi(argv[++argOptions]);

    temp = string(argv[++argOptions]);

    if (temp.substr(0, 2).compare("-I") == 0) {
        //get the input file name
        if (temp.length() > 2) {
            temp = temp.substr(2);
        }
        else {
            if (argc < 5) {
                cout << "Usage: raytrace Width Height -I Filename\n";
                exit(1);
            }
            temp = string(argv[++argOptions]);
        }

        input = temp;
    }
    else {
        cout << "Usage: raytrace Width Height -I Filename\n";
        exit(1);
    }

    cout << width << endl << height << endl << input << endl;

    file.open(input.c_str(), ifstream::in);
    parseScene(file);

    globalScene.debug();

    return 0;
}
